package com.cargosmart.qa.component;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import org.apache.commons.lang.StringEscapeUtils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.junit.Assert;

import com.cargosmart.qa.common.AutoComplete;
import com.cargosmart.qa.config.Config;
import com.cargosmart.qa.exception.SelectLabelNotFoundException;
import com.cargosmart.qa.util.BaseUtil;
import com.cargosmart.qa.util.CollectionUtil;
import com.cargosmart.qa.util.StringUtil;
import com.google.common.base.Function;

import org.openqa.selenium.internal.seleniumemulation.JavascriptLibrary;
public abstract class WebAction {

	private Logger log = Logger.getLogger(getClass().getName());
	protected WebDriver driver;

	protected AutoComplete autoc;

	private WebDriverWait wait;

	protected Config config;
	public WebAction() {
	}

	public WebAction(WebDriver driver) {
		initializeVariables(driver);
	}

	protected void initializeVariables(WebDriver driver) {
		this.driver = driver;
//		this.selenium = new WebDriverBackedSelenium(driver, "");
		this.config = new Config();
		this.wait = new WebDriverWait(driver, config.getWaitTime() / 1000);
		this.autoc = new AutoComplete(driver);
	}

	protected void initializeVariablesWithDefaultSettings(WebDriver driver) {
		this.driver = driver;
		// this.selenium = new WebDriverBackedSelenium(driver, "");
		this.wait = new WebDriverWait(driver, 30000 / 1000);
		this.autoc = new AutoComplete(driver);
	}

	public WebAction config(Config config) {
		this.config = config;
		// this.wait = new WebDriverWait(driver, config.getWaitTime() / 1000);
		return this;
	}

	public WebAction webdriver(WebDriver driver) {
		this.driver = driver;
		return this;
	}

	protected String getDataFromBy(By by) {
		String b = by.toString();
		int index = b.indexOf(":");
		b = b.substring(index + 1).trim();
		return b;
	}

	public void setTimeout(long millis) {
		wait.withTimeout(millis, TimeUnit.MILLISECONDS);
	}

	@Deprecated
	public String getText(String locator) {
		WebElement el = getElement(locator);
		if (el == null) {
			return null;
		}
		return el.getText();
	}

	public String getText(By locator) {
		WebElement el = getElement(locator);
		if (el == null) {
			return null;
		}
		return el.getText();
	}

	public String getValue(By locator) {
		return getAttribute(locator, "value");
	}
	
	public void switchToMainWindow(){
		String whdl = driver.getWindowHandles().iterator().next();
		driver.switchTo().window(whdl);
	}
	
	public String switchToPopupWindow(){
		Set<String> handles = driver.getWindowHandles();
		String originWinHld = driver.getWindowHandle();
		for (String hdl : handles){
			if (hdl != null && !originWinHld.equals(hdl)){
				driver.switchTo().window(hdl);
			}
		}
		return originWinHld;
	}

	public void switchToFrame(String id) {
		driver.switchTo().frame(id);
	}

	public void switchToWindow(String id) {
		driver.switchTo().window(id);
	}

	public void switchToFrameByXpath(String xpath) {
		driver.switchTo().frame(getElement(By.xpath(xpath)));

	}

	public String getCurrentWindowHandler() {
		return driver.getWindowHandle();
	}

	public boolean isElementPresent(WebElement el) {
		try {
			if (el == null) {
				return false;
			}
			/**
			 * Execute isDisplay to determine whether the element is still vaild
			 * in DOM tree, if it is not valid, ElementStaleException will be
			 * throw
			 */
			el.isDisplayed();
			return true;
		} catch (Exception ex) {
			return false;
		}
	}

	public int getInputMaxLength(By input) {
		String max = getAttribute(input, "maxlength");
		if (BaseUtil.isNullOrEmpty(max)) {
			throw new RuntimeException("maxlenght not defined or it is empty.");
		}
		return Integer.parseInt(max);
	}

	@Deprecated
	public boolean isElementPresent(String locator) {
		return (getElement(locator) != null) ? true : false;
	}

	public boolean isElementPresent(By by) {
		return (getElement(by) != null) ? true : false;
	}

	public boolean isElementPresent(By by, WebElement context) {
		return (getElement(by, context) != null) ? true : false;
	}

	@Deprecated
	public void uncheck(String locator) {
		WebElement el = getElement(locator);
		if (el.isSelected()) {
			el.click();
		}
	}

	public void uncheck(By locator) {
		WebElement el = getElement(locator);
		if (el.isSelected()) {
			el.click();
		}
	}

	@Deprecated
	public void check(String locator) {
		WebElement el = getElement(locator);
		if (!el.isSelected()) {
			el.click();
		}
	}

	public String getAttribute(By locator, String attribute) {
		WebElement el = getElement(locator);
		return el.getAttribute(attribute);
	}

	public String getAttribute(WebElement el, String attribute) {
		return el.getAttribute(attribute);
	}

	public int getXpathCount(String xpath) {
		List<WebElement> els = getElements(By.xpath(xpath));
		return (els == null) ? 0 : els.size();
	}

	public List<WebElement> getElements(By locator) {
		try {
			return driver.findElements(locator);
		} catch (NoSuchElementException ex) {
			return new LinkedList<WebElement>();
		}
	}

	public List<WebElement> getElements(By locator, WebElement context) {
		try {
			if (context == null){
				return driver.findElements(locator);
			}
			return context.findElements(locator);
		} catch (NoSuchElementException ex) {
			return new LinkedList<WebElement>();
		}
	}

	public void check(By locator) {
		WebElement el = getElement(locator);
		if (!el.isSelected()) {
			el.click();
		}
	}
	
	private Function<WebDriver, Boolean> isTextPresentInBody(final String text) {
		return new Function<WebDriver, Boolean>() {
			public Boolean apply(WebDriver driver) {
				//				return getElement(By.tagName("body")).getText().contains(text);
				return isTextPresent(text);
			}
		};
	}

	public boolean isTextPresent(String text) {
		boolean textPresent = false;
		if (text == null || "".equals(text)) {
			textPresent = true;
		}
		/**
		 * Search the body of a page, the last one:
		 * By.xpath("//*[text()[contains(.,'" + text + "')]]") could cause
		 * unresponsive script with text is an empty string "". The performance
		 * is similar with (body.getText().contains(text));
		 * 
		 * Avoid using driver.getPageSource().contains(text); which could not
		 * always get the correct result For non-breakable-blank-space in html
		 * " &nbsp; " it needs special handle in xpath with selenium replace
		 * blank space with \u00A0 (a java representation of nbsp)
		 */
		text = StringUtil.normalized(text);
		By[] locators = {
				By.xpath("//*[text()[contains(normalize-space(.),'" + text
						+ "')]]"),
				By.xpath("//*[text()[contains(normalize-space(translate(.,'\u00A0',' ')),'"
						+ text + "')]]") };
		for (By locator : locators) {
			if (textPresent) {
				break;
			}
			List<WebElement> els = getElements(locator,
					getElement(By.tagName("body")));
			if (!CollectionUtil.isNullOrEmpty(els)) {
				for (WebElement el : els) {
					try {
						if (el.isDisplayed()) {
							textPresent = true;
							break;
						}
					} catch (StaleElementReferenceException ex) {
						// ignored
						textPresent = false;
					}
				}
			}
		}
		return textPresent;
	}

	public WebElement getElement(By by) {
		return getElement(by, null);
	}

	public WebElement getElement(By by, WebElement context) {
		try {
			if (context == null) {
				return driver.findElement(by);
			}
			return context.findElement(by);
		} catch (NoSuchElementException ex) {
			return null;
		}
	}

	public boolean isVisible(By by) {
		WebElement el = getElement(by);
		if (el == null) {
			return false;
		}
		return el.isDisplayed();
	}

	@Deprecated
	public boolean isVisible(String by) {
		WebElement el = getElement(by);
		if (el == null) {
			return false;
		}
		return el.isDisplayed();
	}

	public void sleep(long millisSec) {
		try {
			Thread.sleep(millisSec);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	public void maximizeWindow() {
		String js = "if (window.screen){window.moveTo(0, 0);window.resizeTo(window.screen.availWidth,window.screen.availHeight);};";
		((RemoteWebDriver) driver).executeScript(js, "");
	}

	/**
	 * Will cause unexpected error in new WebDriver
	 */
	@Deprecated
	public WebElement getElement(String locator) {
		WebElement el = null;
		try {
			el = driver.findElement(By.id(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		}
		try {
			el = driver.findElement(By.name(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		}
		try {
			el = driver.findElement(By.xpath(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		}
		try {
			el = driver.findElement(By.linkText(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		}
		try {
			el = driver.findElement(By.partialLinkText(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		}
		try {
			el = driver.findElement(By.tagName(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		}
		try {
			el = driver.findElement(By.className(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		} catch (WebDriverException ex) {
			System.out.println("By.className exception");
		}
		try {
			el = driver.findElement(By.cssSelector(locator));
			if (el != null) {
				return el;
			}
		} catch (NoSuchElementException ex) {
		} catch (WebDriverException ex) {
			System.out.println("By.cssSelector exception");
		}
		return el;
	}

	public boolean clickWhenVisible(By locator) {
		WebElement el = getElement(locator);
		boolean clicked = false;
		try {
			if (el != null && el.isDisplayed()) {
				click(el);
				clicked = true;
			}
		} catch (Exception ex) {
			// Ignore
			clicked = false;
		}
		return clicked;
	}

	@Deprecated
	public void click(String locator) {
		getElement(locator).click();
	}

	public void click(WebElement element) {
		element.click();
	}

	public void click(By locator) {
		getElement(locator).click();
	}

	public void click(By xpath, WebElement context) {
		getElement(xpath, context).click();
	}

	public void clickAndWaitVisible(By clickLocator, By waitLocator,
			int repeat, long waitMillis) {
		/**
		 * With Selenium 2.3, for Firefox 5, sometime needs to click twice, so
		 * this ugly function is born....
		 */
		for (int i = 0; i < repeat; i++) {
			click(clickLocator);
			try {
				waitForElementVisible(waitLocator, waitMillis);
				break;
			} catch (TimeoutException ex) {
				// Skip this, continue to next round of click and wait
			}
		}
	}

	public void acceptAlert() {
		try {
			/**
			 * In Firefox, there is timing issue, force to wait for alter to
			 * popup
			 */
			Thread.sleep(500);
			//
			Alert alert = driver.switchTo().alert();
			alert.accept();
		} catch (NoAlertPresentException ex) {
			// Since 2.7.0, this exception is thrown
		} catch (WebDriverException ex) {
			// Pre Selenium 2.7.0
			if (ex.getMessage().contains("alert")) {
				// We're fine with this case: No Alert Window Present,
				// We looking for "No alert is active" to determination
			} else {
				// For other cases, maybe other error
				throw ex;
			}
		} catch (InterruptedException e) {
			// Threading fails.
		}
	}

	public void acceptAlertWhenMsgPresent(String msg) {
		/**
		 * In Firefox, there is timing issue, force to wait for alter to popup
		 */
		try {
			Thread.sleep(500);
		} catch (Exception e) {

		}
		Alert alert = driver.switchTo().alert();
		try {
			if (alert.getText().equals(msg)) {
				alert.accept();
			}
		} catch (WebDriverException ex) {
			if (ex.getMessage().contains("alert")) {
				// We're fine with this case: No Alert Window Present,
				// We looking for "No alert is active" to determination
			} else {
				// For other cases, maybe other error
				throw ex;
			}
		}
	}
	
	public String getFirstSelectedOptionLable(By locator){
		return new Select(getElement(locator)).getFirstSelectedOption().getText();
	}

	public void select(By locator, String label) {
//		WebElement el = getElement(locator);
//		select(el, label);
		int offset = calculateOptionOffset(locator, label);
		selectOption(locator, offset, false);
	}
	
	public void selectWithAlertAccept(By locator, String label) {
		int offset = calculateOptionOffset(locator, label);
		selectOption(locator, offset, true);
	}
	
	private int calculateOptionOffset(By locator, String label){
		int offset = 0;
		Select selectEl = new Select(getElement(locator));
		List<WebElement> options = selectEl.getOptions();
		int selectedIdx = 0;
		int targetIdx = 0;
		boolean selFound = false;
		boolean tarFound = false;
		WebElement seletedOp = selectEl.getFirstSelectedOption();
		for (int i = 0; i < options.size(); i++){
			WebElement option = options.get(i);
			if (option.getText().equals(label)){
				targetIdx = i;
				tarFound = true;
			}
			if (seletedOp.getText().equals(option.getText())){
				selectedIdx = i;
				selFound = true;
			}
			if (selFound && tarFound){
				break;
			}
		}
		if (!tarFound){
			throw new SelectLabelNotFoundException(label + " not found");
		}
		if (!selFound){
			selectEl.selectByIndex(0);
		}
		offset = targetIdx - selectedIdx;
		return offset;
	}
	
	private void selectOption(By locator, int offset, boolean needsToAcceptAlert){
		if (offset != 0){
			Keys key = Keys.ARROW_DOWN;
			if (offset < 0){
				key = Keys.ARROW_UP;
				offset *= -1;
			}
			for (int i = 0; i < offset; i++){
				getElement(locator).sendKeys(key);				
				if(needsToAcceptAlert && isAlertPresent()){
					acceptAlert();
					waitForPageToLoad();
					sleep(50);
				}
			}
		}
	}

	public void select(By locator, int zeroBasedIndex) {
		Select selectEl = new Select(getElement(locator));
		List<WebElement> options = selectEl.getOptions();
		int selectedIdx = 0;
		WebElement seletedOp = selectEl.getFirstSelectedOption();
		for (int i = 0; i < options.size(); i++){
			WebElement option = options.get(i);
			if (seletedOp.getText().equals(option.getText())){
				selectedIdx = i;
				break;
			}
		}		
		int offset = zeroBasedIndex - selectedIdx;
		selectOption(locator, offset, false);
	}
	
	public void selectWithAlertAccept(By locator, int zeroBasedIndex) {
		Select selectEl = new Select(getElement(locator));
		List<WebElement> options = selectEl.getOptions();
		int selectedIdx = 0;
		WebElement seletedOp = selectEl.getFirstSelectedOption();
		for (int i = 0; i < options.size(); i++){
			WebElement option = options.get(i);
			if (seletedOp.getText().equals(option.getText())){
				selectedIdx = i;
				break;
			}
		}		
		int offset = zeroBasedIndex - selectedIdx;
		selectOption(locator, offset, true);
	}
	
	@Deprecated
	public void input(String locator, String data) {
		input(getElement(locator), data);
	}

	public void input(By locator, String data) {
		input(getElement(locator), data);
	}

	public void input(By locator, WebElement context, String data) {
		input(getElement(locator, context), data);
	}

	public void input(WebElement el, String data) {
		el.clear();
		el.sendKeys(data);
	}

	public void inputWithoutClear(By locator, String data) {
		getElement(locator).sendKeys(data);
	}

	/**
	 * Run js to set the text, this method is not recommended. Since there are
	 * some js event binding to the component, just setting the text will
	 * missing the event. For most of time, please use type(By locator, String
	 * data) if you want to set a text
	 * 
	 * @param id
	 * @param data
	 */
	public void setTextForInput(String id, String data) {
		runScript("document.getElementById(\"" + id + "\").value = \"" + data
				+ "\"");
	}

	/**
	 * Run js to set the text, this method is not recommended. Since there are
	 * some js event binding to the component, just setting the text will
	 * missing the event. For most of time, please use type(By locator, String
	 * data)
	 * 
	 * @param id
	 * @param data
	 */
	public void setTextForTextArea(String id, String data) {
		runScript("document.getElementById(\"" + id + "\").innerHTML = \""
				+ data + "\"");
	}

	public void typeWithoutClear(By locator, String data) {
		getElement(locator).sendKeys(data);
	}
	
	public void type(By locator, String data) {
		input(locator, data);
	}

	public void type(By locator, WebElement context, String data) {
		input(locator, context, data);
	}

	@Deprecated
	public void type(String locator, String data) {
		input(locator, data);
	}

	Function<WebDriver, WebElement> presenceOfElementLocated(final By[] locators) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				for (By locator : locators) {
					try {
						WebElement el = driver.findElement(locator);
						if (el != null) {
							return el;
						}
					} catch (NoSuchElementException ex) {
						// ignore
					}
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> presenceOfElementLocated(final By locator) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				return getElement(locator);
			}
		};
	}

	@Deprecated
	Function<WebDriver, WebElement> presenceOfElementLocated(
			final String locator) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				return getElement(locator);
			}
		};
	}

	Function<WebDriver, WebElement> invisibleOfOrNonExistElementLocated(
			final By[] locators) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				int i = 0;
				WebElement nonNullEl = null;
				for (By locator : locators) {
					try {
						WebElement el = driver.findElement(locator);
						if (el == null) {
							i++;
						} else if (!el.isDisplayed()) {
							i++;
							nonNullEl = el;
						}
					} catch (StaleElementReferenceException ex) {
						ex.printStackTrace();
					} catch (NoSuchElementException ex) {
						i++;
					}
				}
				if (i == locators.length) {
					return nonNullEl;
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> invisibleOfElementLocated(
			final By[] locators) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				int i = 0;
				WebElement el = null;
				for (By locator : locators) {
					try {
						el = driver.findElement(locator);
						if (el != null && !el.isDisplayed()) {
							i++;
						}
					} catch (StaleElementReferenceException ex) {
						ex.printStackTrace();
					} catch (NoSuchElementException ex) {
						// Ignore
					}
				}
				if (i == locators.length) {
					return el;
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> invisibleOfElementLocated(final By locator) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				WebElement el = getElement(locator);
				try {
					if (el != null && !el.isDisplayed()) {
						return el;
					}
				} catch (StaleElementReferenceException ex) {
					ex.printStackTrace();
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> disabledOfElement(final By locator) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				WebElement el = getElement(locator);
				try {
					if (el != null && !el.isEnabled()) {
						return el;
					}
				} catch (StaleElementReferenceException ex) {
					ex.printStackTrace();
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> enabledOfElement(final By locator) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				WebElement el = getElement(locator);
				try {
					if (el != null && el.isEnabled()) {
						return el;
					}
				} catch (StaleElementReferenceException ex) {
					ex.printStackTrace();
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> visibleOfElementLocated(final By locator) {
		return visibleOfElementLocated(locator, null);
	}

	Function<WebDriver, WebElement> visibleOfElementLocated(final By locator,
			final WebElement context) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				WebElement el = getElement(locator, context);
				try {
					if (el != null && el.isDisplayed()) {
						return el;
					}
				} catch (StaleElementReferenceException ex) {
					ex.printStackTrace();
				}
				return null;
			}
		};
	}

	Function<WebDriver, WebElement> visibleOfElementLocated(final WebElement el) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				try {
					if (el != null && el.isDisplayed()) {
						return el;
					}
				} catch (StaleElementReferenceException ex) {
					ex.printStackTrace();
					return null;
				}
				return null;
			}
		};
	}

	@Deprecated
	Function<WebDriver, WebElement> visibleOfElementLocated(final String locator) {
		return new Function<WebDriver, WebElement>() {
			public WebElement apply(WebDriver driver) {
				return getElement(locator);
			}
		};
	}

	@Deprecated
	public WebElement waitForElement(String locator) {
		return wait.until(presenceOfElementLocated(locator));
	}

	public WebElement waitForElement(By locator) {
		return wait.until(presenceOfElementLocated(locator));
	}

	public WebElement waitForElement(By[] locators) {
		return wait.until(presenceOfElementLocated(locators));
	}

	@Deprecated
	public WebElement waitForElementVisible(String locator) {
		return wait.until(visibleOfElementLocated(locator));
	}

	public WebElement waitForElementVisible(By locator, long millis) {
		WebDriverWait wait = getWebDriverWait(millis);
		return wait.until(visibleOfElementLocated(locator));
	}

	public WebElement waitForElementVisible(By locator, WebElement context,
			long millis) {
		WebDriverWait wait = getWebDriverWait(millis);
		return wait.until(visibleOfElementLocated(locator, context));
	}

	public WebElement waitForElementVisible(By locator, WebElement context) {
		return wait.until(visibleOfElementLocated(locator, context));
	}

	public WebElement waitForElementVisible(WebElement element) {
		return wait.until(visibleOfElementLocated(element));
	}

	public WebElement waitForElementVisible(WebElement element, long millis) {
		WebDriverWait wait = new WebDriverWait(driver, millis / 1000);
		return wait.until(visibleOfElementLocated(element));
	}

	public WebElement waitForElementVisible(By locator) {
		return wait.until(visibleOfElementLocated(locator));
	}

	public WebElement waitForElementInvisible(By locator) {
		return wait.until(invisibleOfElementLocated(locator));
	}

	public WebElement waitForElementInvisible(By locator, long millis) {
		WebDriverWait wait = getWebDriverWait(millis);
		return wait.until(invisibleOfElementLocated(locator));
	}

	private WebDriverWait getWebDriverWait(long millis) {
		WebDriverWait wait = null;
		if (millis == config.getWaitTime()) {
			wait = this.wait;
		}
		wait = new WebDriverWait(driver, millis / 1000);
		return wait;
	}

	public void waitForElementInvisible(By[] locators) {
		wait.until(invisibleOfElementLocated(locators));
	}

	public void waitForElementNonExistOrInvisible(By[] locators) {
		wait.until(invisibleOfOrNonExistElementLocated(locators));
	}

	public WebElement waitForElementDisabled(By locator, long millis) {
		WebDriverWait wait = getWebDriverWait(millis);
		return wait.until(disabledOfElement(locator));
	}

	public WebElement waitForElementDisabled(By locator) {
		return wait.until(disabledOfElement(locator));
	}

	public WebElement waitForElementEnabled(By locator, long millis) {
		WebDriverWait wait = getWebDriverWait(millis);
		return wait.until(enabledOfElement(locator));
	}

	public WebElement waitForElementEnabled(By locator) {
		return wait.until(enabledOfElement(locator));
	}

	public void waitForPageToLoad(long timeOutInMillis) {
		waitForElementInvisible(By.id("dialog"), timeOutInMillis);
	}

	public void waitForPageToLoad1(long timeOutInMillis) {
		// Wait until things look like they've been stable for "timeToWait"
		if (!(driver instanceof JavascriptExecutor)) {
			// Assume that we Do The Right Thing
			return;
		}
		long started = System.currentTimeMillis();
		while (true) {
			try {
				Object result = ((JavascriptExecutor) driver)
						.executeScript("return document['readyState'] ? 'complete' == document.readyState : true");
				if (result != null && result instanceof Boolean
						&& (Boolean) result) {
					return;
				}
				if (System.currentTimeMillis() - started > timeOutInMillis) {
					return;
				}
				Thread.sleep(200);
			} catch (Exception e) {
				// Possible page reload. Fine
			}
		}
	}

	public void waitForPageToLoad() {
		// By layer_std = By.id("pop_layer_1");
		// By layer_acz = By.id("dialog");
		// if (getElement(layer_std) != null){
		// waitForElementInvisible(layer_std);
		// }
		// if (getElement(layer_acz) != null){
		// waitForElementInvisible(layer_acz);
		// }
		waitForPageToLoad(config.getWaitTime());
		sleep(300);
	}

	public void moveTo(WebElement el) {
		moveTo(el, 0, 0);
	}

	public void moveTo(WebElement el, int x_offset, int y_offset) {
		if (driver instanceof FirefoxDriver) {
			FirefoxDriver fd = (FirefoxDriver) driver;
			fd.getMouse().mouseMove(((RemoteWebElement) el).getCoordinates(),
					x_offset, y_offset);
		} else {
			Actions a = new Actions(driver);
			a = a.moveToElement(el, x_offset, y_offset);
			a.perform();
		}
	}

	public void gotoMenuFromUI(String... menuTitles) {
		String menuString = "";
		/*
		 * String[] menuXPaths = { "//table[@class='menubg']//td[text()='",
		 * "../..//td[text()='", "..//td[text()='" };
		 */
		for (int i = 0; i < menuTitles.length; i++) {
			menuTitles[i] = menuTitles[i].toUpperCase();
		}
		String[] menuXPaths = {
				"//table[@class='menubg']//td[normalize-space(translate(text(),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'))='",
				"../..//td[normalize-space(translate(text(),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'))='",
				"./following-sibling::td//td[normalize-space(translate(text(),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'))='" };
		int t = 0;
		if (driver instanceof FirefoxDriver) {
			t = 1;
		}
		Actions a = new Actions(driver);
		WebElement el = null;
		WebElement parent = null;
		switch (t) {
		case 0:
			for (int i = 0; i < menuTitles.length; i++) {
				menuString = menuXPaths[i] + menuTitles[i] + "']";
				el = getElement(By.xpath(menuString), parent);
				if (el == null) {
					Assert.fail(menuTitles[i] + " not found. xpath:"
							+ menuString);
				}
				parent = el;

				if (i == menuTitles.length - 1) {
					continue;
				}
				a = a.moveToElement(el, 5, 5);
			}
			a.perform();
			el.click();
			break;
		case 1:
			/**
			 * Firefox could not perform click properly, using JS script to
			 * trigger to stimulate the click If fail to make Menu shows up(Menu
			 * does exist, but for some reason, could not bring it up), it will
			 * retry, start from beging again. Retry currently set to 5
			 */
			FirefoxDriver fd = (FirefoxDriver) driver;
			// final long MENU_TIME_OUT = 15000l;
			for (int i = 0; i < menuTitles.length; i++) {
				menuString = menuXPaths[i] + menuTitles[i] + "']";
				// el = waitForElementVisible(By.xpath(menuString), parent,
				// MENU_TIME_OUT);
				el = getElement(By.xpath(menuString), parent);
				if (el == null) {
					Assert.fail(menuTitles[i] + " not found. xpath:"
							+ menuString);
				}
				parent = el;

				if (el == null) {
					Assert.fail(menuTitles[i] + " not found.");
				}

				if (i == menuTitles.length - 1) {
					String script = el.getAttribute("onclick");
					/**
					 * Click the body to clear the state cache Using el.click()
					 * sometimes failed.. so use runscript instead but runscript
					 * will have menu open state cache, to click the body to let
					 * the menu hide
					 */
					try {
						getElement(By.tagName("body")).click();
					} catch (Exception ex) {
						// Skip any exception
					}
					/**
					 * End
					 */
					runScript(script);
				} else {
					// fd.getMouse().mouseMove(((FirefoxWebElement)el).getCoordinates());

					Actions aa = new Actions(fd);
					aa.moveToElement(el).build().perform();
					/*
					 * fd.getMouse().mouseMove( ((RemoteWebElement)
					 * el).getCoordinates());
					 */
				}
			}

			break;
		}
		waitForPageToLoad();
		WebElement element = waitForElement(new By[] {
				By.xpath("//a[contains(@href,'javascript:signOffConfirm()')]"),
				By.xpath("//td[@class='pageTitle' and text()='Error Message']"),
				By.linkText("Hot Shipment Management")// This is for Internal
				// support page
				, By.xpath("//h1[text()='404 Not Found']") });
		if ("Error Message".equals(element.getText())) {
			Assert.fail("Error Source:" + getErrorSource());
		} else if ("404 Not Found".equals(element.getText())) {
			Assert.fail("404 Not Found");
		}
	}

	private String getErrorSource() {
		WebElement title = getElement(By
				.xpath("//td[@class='labelText' and text()='Error Source:']"));
		return title.findElement(
				By.xpath("..//td[@nowrap='nowrap' and @valign='top']"))
				.getText();
	}

	@Deprecated
	public void mouseOver(String locator) {
		WebElement el = getElement(locator);
		((InternetExplorerDriver) driver).getMouse().mouseMove(
				((RemoteWebElement) el).getCoordinates());
	}

	public void mouseOver(WebElement el) {
		((InternetExplorerDriver) driver).getMouse().mouseMove(
				((RemoteWebElement) el).getCoordinates());
	}

	public void mouseOver(By locator) {
		WebElement el = getElement(locator);
		((InternetExplorerDriver) driver).getMouse().mouseMove(
				((RemoteWebElement) el).getCoordinates());
	}

	public int getElementCount(By locator) {
		List<WebElement> els = getElements(locator);
		if (els == null) {
			return 0;
		}
		return els.size();
	}

	public void assertElementPresent(By locator) {
		Assert.assertNotNull(getElement(locator));
	}

	public void assertElementNotPresent(By locator) {
		try {
			Assert.assertNull(getElement(locator));
		} catch (NullPointerException ex) {
			ex.printStackTrace();
		} catch (Exception ex) {
		}
	}

	public boolean isConfirmationPresent() {
		return false;
	}

	public String getInnerHtml(WebElement el) {
		return (String) ((JavascriptExecutor) driver).executeScript(
				"return $(arguments[0]).html()", el);
	}

	public String getInnerHtmlWithDecode(WebElement el) {
		String html = (String) ((JavascriptExecutor) driver).executeScript(
				"return $(arguments[0]).html()", el);
		if (BaseUtil.isNullOrEmpty(html)) {
			return html;
		}
		return StringEscapeUtils.unescapeHtml(html);
	}

	public String getInnerHtmlWithDecodeFromShowTips(WebElement el) {
		String id = getAttribute(el, "id");
		if (BaseUtil.isNullOrEmpty(id)) {
			id = "";
		}
		id += "TipsLayer";
		/**
		 * Before call moveTo, click the element first, in the environment with
		 * of two screens, the browser may not be focus, then it will cause
		 * moveTo fail.
		 */
		click(el);

		/**
		 * To avoid a bug from CS common js function, move to a safe point,
		 * otherwise, the tips will keep flashing on screen due to the over
		 * layer block with the icon, cause mouseout event trigger, after
		 * mouseout event, since the mouse is still pointing to the icon,
		 * mouseover trigger, and then mouseout straight away, infinite loop...
		 */
		moveTo(el, el.getSize().getWidth() / 2, el.getSize().getHeight() / 2);
		return getElement(By.id(id)).getText();
	}

	public Object runScript(String script) {
		return ((RemoteWebDriver) driver).executeScript(script, "");
	}

	public boolean waitForTextPresent(String text, int millseconds) {
		return getWebDriverWait(millseconds).until(isTextPresentInBody(text)); 
	}
	
	public boolean waitForTextPresent(String text) {
		return getWebDriverWait(config.getWaitTime()).until(isTextPresentInBody(text)); 
	}

	public void pause(int milliSec) {
		try {
			Thread.sleep(milliSec);
		} catch (InterruptedException e) {
			;
		}
	}

	public boolean isAlertPresent() {
		try {
			driver.switchTo().alert();
			return true;
		} catch (NoAlertPresentException e) {
			// TODO: handle exception
			return false;
		}
	}

	public void clickAndSelectModalDialog() {
		Alert confirm = driver.switchTo().alert();
		confirm.accept();
//		confirm.dismiss();
	}
	
	public List<String> getSelectBoxAllOptionValue(By locator){
		Select select = new Select(driver.findElement(locator));
		List<WebElement> options = select.getOptions();
		List<String> list = new ArrayList<String>();
		for(int i = 0 ; i < options.size() ; i ++){
			if(options.get(i).getText().equals("All")){
				
			}else{
				list.add(options.get(i).getText().trim());
			}
		}
		return list;
	}
	public void focus(By locator){
		JavascriptLibrary javascript = new JavascriptLibrary();
		javascript.callEmbeddedSelenium(driver, "triggerEvent", getElement(locator), "blur");
//		driver.execute_script("$('#su').click()")
	}

	public void assertConfirmation(String target) {

		try{
			if (waitForAlertPresent(target)){
				Alert a = driver.switchTo().alert();
				Assert.assertEquals(a.getText(), target);
				a.accept();
			}
		}catch(Exception ex){
			/**
			 * ignore any exception: click / clickAndWait / select will accept alert by default
			 * which will cause this method throw exception
			 * for case click then assertConfirmation will pass with IDE, 
			 * so it also needs to pass with WebDriver implementation
			 */
			log.info(ex.getMessage());
		}
	}
	public boolean waitForAlertPresent(String target) {
		boolean isAlertPresent = false;
		long timeElpsed = 0l;
		long periodToCheck = 1000;
		while (timeElpsed <= 5000 && !isAlertPresent){
			try{
				Alert a = driver.switchTo().alert();
				if (a != null){
					isAlertPresent = true;
					break;
				}
			}catch(Exception ex){
			}			
			sleep(periodToCheck);
			timeElpsed += periodToCheck;
		}
		return isAlertPresent;
	}
}
